﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml;

namespace dk.Helper.Base.Core
{
    public class WorldMapSettings : IWorldMapSettings
    {
        private readonly SolidBrush abandonedVillageColor = new SolidBrush(Color.DarkGray);
        private readonly IDictionary<IAlly, SolidBrush> brushesByAlly;
        private readonly IDictionary<IPlayer, SolidBrush> brushesByPlayer;
        private readonly SolidBrush commonVillageBrush = new SolidBrush(Color.DarkRed);
        private readonly IWorld world;
        private string worldDir;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Object"/> class.
        /// </summary>
        public WorldMapSettings(IWorld world, IWorldMapFilter worldMapFilter)
        {
            this.world = world;
            Allies = new Allies();
            Enemies = new Allies();
            AllyPlayers = new Players();
            EnemyPlayers = new Players();
            brushesByAlly = new Dictionary<IAlly, SolidBrush>();
            brushesByPlayer = new Dictionary<IPlayer, SolidBrush>();
            WorldMapFilter = new WorldMapFilter(world);
            Zoom = 4;
            MapCenterX = 500;
            MapCenterY = 500;
            WorldMapFilter = worldMapFilter;
            Load();
        }

        #region IWorldMapSettings Members

        /// <summary>
        /// Gets or sets the zoom.
        /// </summary>
        /// <value>
        /// The zoom.
        /// </value>
        public int Zoom { get; set; }

        /// <summary>
        /// Gets or sets the map center X.
        /// </summary>
        /// <value>
        /// The map center X.
        /// </value>
        public int MapCenterX { get; set; }

        /// <summary>
        /// Gets or sets the map center Y.
        /// </summary>
        /// <value>
        /// The map center Y.
        /// </value>
        public int MapCenterY { get; set; }

        /// <summary>
        /// Gets the world map filter.
        /// </summary>
        public IWorldMapFilter WorldMapFilter { get; private set; }

        /// <summary>
        /// Gets the allies.
        /// </summary>
        public IAllies Allies { get; private set; }

        /// <summary>
        /// Gets the enemies.
        /// </summary>
        public IAllies Enemies { get; private set; }

        /// <summary>
        /// Gets the ally players.
        /// </summary>
        public IPlayers AllyPlayers { get; private set; }

        /// <summary>
        /// Gets the enemy players.
        /// </summary>
        public IPlayers EnemyPlayers { get; private set; }

        /// <summary>
        /// Sets the brush of the ally.
        /// </summary>
        /// <param name="ally">The ally.</param>
        /// <param name="brush">The brush.</param>
        public void SetBrush(IAlly ally, SolidBrush brush)
        {
            if (!brushesByAlly.ContainsKey(ally))
            {
                brushesByAlly.Add(ally, brush);
            }
            else
            {
                brushesByAlly[ally] = brush;
            }
            Save();
        }

        /// <summary>
        /// Gets the brush of the ally.
        /// </summary>
        /// <param name="ally">The ally.</param>
        /// <returns></returns>
        public SolidBrush GetBrush(IAlly ally)
        {
            return !brushesByAlly.ContainsKey(ally) ? commonVillageBrush : brushesByAlly[ally];
        }

        /// <summary>
        /// Sets the brush.
        /// </summary>
        /// <param name="player">The player.</param>
        /// <param name="brush">The brush.</param>
        public void SetBrush(IPlayer player, SolidBrush brush)
        {
            if (!brushesByPlayer.ContainsKey(player))
            {
                brushesByPlayer.Add(player, brush);
            }
            else
            {
                brushesByPlayer[player] = brush;
            }
            Save();
        }

        /// <summary>
        /// Gets the brush.
        /// </summary>
        /// <param name="player">The player.</param>
        /// <returns></returns>
        public SolidBrush GetBrush(IPlayer player)
        {
            return !brushesByPlayer.ContainsKey(player) ? commonVillageBrush : brushesByPlayer[player];
        }

        /// <summary>
        /// Gets the brush of the village.
        /// </summary>
        /// <param name="village">The village.</param>
        /// <returns></returns>
        public SolidBrush GetVillageBrush(IVillage village)
        {
            if (village.PlayerID == 0)
            {
                return abandonedVillageColor;
            }
            IPlayer player = world.Players.PlayersByID[village.PlayerID];
            if (brushesByPlayer.ContainsKey(player))
            {
                return brushesByPlayer[player];
            }
            if (player.AllyID != 0)
            {
                IAlly ally = world.Allies.AlliesByID[player.AllyID];
                if (brushesByAlly.ContainsKey(ally))
                {
                    return brushesByAlly[ally];
                }
            }
            return commonVillageBrush;
        }

        /// <summary>
        /// Removes the specified ally.
        /// </summary>
        /// <param name="ally">The ally.</param>
        public void Remove(IAlly ally)
        {
            Allies.Remove(ally);
            Enemies.Remove(ally);
            if (brushesByAlly.ContainsKey(ally))
                brushesByAlly.Remove(ally);
        }

        /// <summary>
        /// Removes the specified player.
        /// </summary>
        /// <param name="player">The player.</param>
        public void Remove(IPlayer player)
        {
            AllyPlayers.Remove(player);
            EnemyPlayers.Remove(player);
            if (brushesByPlayer.ContainsKey(player))
                brushesByPlayer.Remove(player);
        }

        /// <summary>
        /// Saves this instance.
        /// </summary>
        public void Save()
        {
            CreateDirectories();

            var xmlWriterSettings = new XmlWriterSettings();
            xmlWriterSettings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(Path.Combine(worldDir, "map_settings.xml"), xmlWriterSettings))
            {
                SaveSettings(writer);
            }
        }

        /// <summary>
        /// Loads this instance.
        /// </summary>
        public void Load()
        {
            CreateDirectories();

            string filename = Path.Combine(worldDir, "map_settings.xml");
            if (!File.Exists(filename))
                return;

            using (TextReader input = new StreamReader(filename))
            {
                var doc = new XmlDocument();
                doc.Load(input);

                ParseXmlNodeToMap(doc.SelectSingleNode("//settings/map"));
                ParseXmlNodeToFilter(doc.SelectSingleNode("//settings/filter"));

                XmlNodeList allyNodeList = doc.SelectNodes("//settings/allies/ally");
                if (allyNodeList != null)
                {
                    foreach (XmlNode xmlNode in allyNodeList)
                    {
                        ParseXmlNodeToAlly(xmlNode);
                    }
                }
                XmlNodeList playerNodeList = doc.SelectNodes("//settings/players/player");
                if (allyNodeList != null)
                {
                    foreach (XmlNode xmlNode in playerNodeList)
                    {
                        ParseXmlNodeToPlayer(xmlNode);
                    }
                }
            }
        }

        #endregion

        /// <summary>
        /// Saves the settings.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void SaveSettings(XmlWriter writer)
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("settings");
            SaveMap(writer);
            SaveFilter(writer);
            SaveAllies(writer);
            SavePlayers(writer);
            writer.WriteEndElement();
            writer.WriteEndDocument();
        }

        /// <summary>
        /// Saves the players.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void SavePlayers(XmlWriter writer)
        {
            writer.WriteStartElement("players");
            foreach (IPlayer player in brushesByPlayer.Keys)
            {
                writer.WriteStartElement("player");

                writer.WriteAttributeString("ID", player.ID.ToString());
                writer.WriteAttributeString("R", brushesByPlayer[player].Color.R.ToString());
                writer.WriteAttributeString("G", brushesByPlayer[player].Color.G.ToString());
                writer.WriteAttributeString("B", brushesByPlayer[player].Color.B.ToString());

                if (AllyPlayers.Contains(player))
                    writer.WriteAttributeString("diplomacy", "ally");
                if (EnemyPlayers.Contains(player))
                    writer.WriteAttributeString("diplomacy", "enemy");

                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// Saves the allies.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void SaveAllies(XmlWriter writer)
        {
            writer.WriteStartElement("allies");
            foreach (IAlly ally in brushesByAlly.Keys)
            {
                writer.WriteStartElement("ally");

                writer.WriteAttributeString("ID", ally.ID.ToString());
                writer.WriteAttributeString("R", brushesByAlly[ally].Color.R.ToString());
                writer.WriteAttributeString("G", brushesByAlly[ally].Color.G.ToString());
                writer.WriteAttributeString("B", brushesByAlly[ally].Color.B.ToString());
                if (Allies.Contains(ally))
                    writer.WriteAttributeString("diplomacy", "ally");
                if (Enemies.Contains(ally))
                    writer.WriteAttributeString("diplomacy", "enemy");

                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// Saves the map.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void SaveMap(XmlWriter writer)
        {
            writer.WriteStartElement("map");
            writer.WriteAttributeString("zoom", Zoom.ToString());
            writer.WriteAttributeString("centerX", MapCenterX.ToString());
            writer.WriteAttributeString("centerY", MapCenterY.ToString());
            writer.WriteEndElement();
        }

        /// <summary>
        /// Saves the filter.
        /// </summary>
        /// <param name="writer">The writer.</param>
        private void SaveFilter(XmlWriter writer)
        {
            writer.WriteStartElement("filter");
            writer.WriteAttributeString("showabadoned", WorldMapFilter.ShowAbandonedVillages.ToString());
            writer.WriteAttributeString("showplayerswithoutally", WorldMapFilter.ShowPlayersWithoutAlly.ToString());
            writer.WriteEndElement();
        }

        /// <summary>
        /// Parses the XML node to map.
        /// </summary>
        /// <param name="xmlNode">The XML node.</param>
        private void ParseXmlNodeToMap(XmlNode xmlNode)
        {
            Zoom = int.Parse(xmlNode.Attributes["zoom"].Value);
            MapCenterX = int.Parse(xmlNode.Attributes["centerX"].Value);
            MapCenterY = int.Parse(xmlNode.Attributes["centerY"].Value);
        }

        /// <summary>
        /// Parses the XML node to filter.
        /// </summary>
        /// <param name="xmlNode">The XML node.</param>
        private void ParseXmlNodeToFilter(XmlNode xmlNode)
        {
            WorldMapFilter.ShowAbandonedVillages = bool.Parse(xmlNode.Attributes["showabadoned"].Value);
            WorldMapFilter.ShowPlayersWithoutAlly = bool.Parse(xmlNode.Attributes["showplayerswithoutally"].Value);
        }

        /// <summary>
        /// Parses the XML node to ally.
        /// </summary>
        /// <param name="xmlNode">The XML node.</param>
        private void ParseXmlNodeToAlly(XmlNode xmlNode)
        {
            int allyID = int.Parse(xmlNode.Attributes["ID"].Value);
            int r = int.Parse(xmlNode.Attributes["R"].Value);
            int g = int.Parse(xmlNode.Attributes["G"].Value);
            int b = int.Parse(xmlNode.Attributes["B"].Value);
            string diplomacy = xmlNode.Attributes["diplomacy"].Value;
            if (!world.Allies.AlliesByID.ContainsKey(allyID))
                return;
            IAlly ally = world.Allies.AlliesByID[allyID];
            var brush = new SolidBrush(Color.FromArgb(r, g, b));
            brushesByAlly.Add(ally, brush);
            if (diplomacy.Equals("ally"))
                Allies.Add(ally);
            if (diplomacy.Equals("enemy"))
                Enemies.Add(ally);
        }

        /// <summary>
        /// Parses the XML node to player.
        /// </summary>
        /// <param name="xmlNode">The XML node.</param>
        private void ParseXmlNodeToPlayer(XmlNode xmlNode)
        {
            int playerID = int.Parse(xmlNode.Attributes["ID"].Value);
            int r = int.Parse(xmlNode.Attributes["R"].Value);
            int g = int.Parse(xmlNode.Attributes["G"].Value);
            int b = int.Parse(xmlNode.Attributes["B"].Value);
            string diplomacy = xmlNode.Attributes["diplomacy"].Value;
            if (!world.Players.PlayersByID.ContainsKey(playerID))
                return;
            IPlayer player = world.Players.PlayersByID[playerID];
            var brush = new SolidBrush(Color.FromArgb(r, g, b));
            brushesByPlayer.Add(player, brush);
            if (diplomacy.Equals("ally"))
                AllyPlayers.Add(player);
            if (diplomacy.Equals("enemy"))
                EnemyPlayers.Add(player);
        }

        /// <summary>
        /// Creates the directories.
        /// </summary>
        private void CreateDirectories()
        {
            string appDataDir = Environment.GetEnvironmentVariable("APPDATA");
            if (appDataDir == null)
                throw new DirectoryNotFoundException("APPDATA environment variable not defined!");

            string dkHelperDir = Path.Combine(appDataDir, "dk.Helper");
            if (!Directory.Exists(dkHelperDir))
                Directory.CreateDirectory(dkHelperDir);

            string dataDir = Path.Combine(dkHelperDir, "data");
            if (!Directory.Exists(dataDir))
                Directory.CreateDirectory(dataDir);

            worldDir = Path.Combine(dataDir, world.Name);
            if (!Directory.Exists(worldDir))
                Directory.CreateDirectory(worldDir);
        }
    }
}